/*
 * Copyright (c) 2007 Easter.
 * 
 * Author: Yichao Zhang
 * 
 * Email: Yichao.Zhang@gmail.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.easter.blogsync;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.xmlrpc.XmlRpcException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.sun.org.apache.xerces.internal.dom.DocumentImpl;
import com.sun.org.apache.xpath.internal.XPathAPI;

public class RSSReader {
  private File rssFile = null;

  public List<WordpressEntry> getRecentPosts(int from, int to) throws Exception {
    return getRecentPosts(from, to, true);
  }

  /**
   * get recent posts
   * 
   * @param num set how many recent posts do you want to get
   * @param to
   * @param draft true when get both draft and published posts; false when get
   *          only published posts
   * @return
   * @throws MalformedURLException
   * @throws XmlRpcException
   */
  public List<WordpressEntry> getRecentPosts(int from, int to, boolean draft)
      throws Exception {
    List<WordpressEntry> ret = new ArrayList<WordpressEntry>();
    DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
    f.setCoalescing(true);
    DocumentBuilder dombuilder = f.newDocumentBuilder();
    InputStream is = new FileInputStream(rssFile);
    Document doc = dombuilder.parse(is);
    String itemsXPath = "/rss/channel/item";
    NodeList nodeList = XPathAPI.selectNodeList(doc, itemsXPath);
    Node contentNode = createNamespaceContext(new String[] {
        "xmlns:content", "http://purl.org/rss/1.0/modules/content/"});
    Node wpNode = createNamespaceContext(new String[] {
        "xmlns:wp", "http://wordpress.org/export/1.0/"});
    for (int i = 0; i < nodeList.getLength(); i++) {
      if (i < from) {
        continue;
      }
      if (i >= to) {
        break;
      }

      Node n = nodeList.item(i);
      String postType = findSingleValue(n, "post_type", wpNode);
      if (!"post".equals(postType)) {
        continue;
      }

      WordpressEntry entry = new WordpressEntry();
      String title = findSingleValue(nodeList.item(i), "title");
      List<String> categories = findValue(n, "category");
      String content = findSingleValue(n, "encoded", contentNode);
      String postDate = findSingleValue(n, "post_date", wpNode);

      NodeList comments = XPathAPI.selectNodeList(n, "comment");
      WordpressComment[] cs = new WordpressComment[comments.getLength()];
      for (int j = 0; j < comments.getLength(); j++) {
        Node comment = comments.item(j);
        WordpressComment c = new WordpressComment();
        c.setAuthor(findSingleValue(comment, "comment_author", wpNode));
        c.setUrl(findSingleValue(comment, "comment_author_url", wpNode));
        c.setContent(findSingleValue(comment, "comment_content", wpNode));
        cs[j] = c;
      }
      entry.setComments(cs);
      
      entry.setCategories(categories.toArray());
      entry.setDateCreated(parseDate(postDate));
      entry.setDescription(content);
      entry.setPostid("post-" + i);
      entry.setTitle(title);
      entry.setUserid("");
      if (draft || !isDraft(entry)) {
        ret.add(entry);
      }
    }
    is.close();
    return ret;
  }

  private String findSingleValue(Node node, String xpath, Node n1)
      throws Exception {
    if ((xpath == null) || (xpath.length() == 0)) {
      throw new Exception("xpath can not be null");
    }
    if (node == null) {
      throw new Exception("The node can not be null");
    }
    return getTextContents(XPathAPI.selectSingleNode(node, xpath, n1));
  }

  public String getTextContents(Node node) {
    NodeList childNodes;
    StringBuffer contents = new StringBuffer();
    childNodes = node.getChildNodes();
    for (int i = 0; i < childNodes.getLength(); i++) {
      if (childNodes.item(i).getNodeType() == Node.TEXT_NODE) {
        contents.append(childNodes.item(i).getNodeValue());
      }
    }
    return contents.toString();
  }

  public String findSingleValue(Node node, String xpath) throws Exception {
    if ((xpath == null) || (xpath.length() == 0)) {
      throw new Exception("xpath can not be null");
    }
    if (node == null) {
      throw new Exception("The node can not be null");
    }
    return getTextContents(XPathAPI.selectSingleNode(node, xpath));
  }

  public List<String> findValue(Node node, String xpath) throws Exception {
    List<String> retList = new ArrayList<String>();
    if ((xpath == null) || (xpath.length() == 0)) {
      throw new Exception("xpath can not be null");
    }
    if (node == null) {
      throw new Exception("The node can not be null");
    }
    NodeList nodeList = XPathAPI.selectNodeList(node, xpath);
    for (int i = 0; i < nodeList.getLength(); i++) {
      retList.add(getTextContents(nodeList.item(i)));
    }
    return retList;
  }

  public String findXPath(Node node) {
    StringBuffer sb = new StringBuffer();
    while (!(node instanceof Document)) {
      sb.insert(0, node.getNodeName());
      sb.insert(0, "/");
      node = node.getParentNode();
    }
    return sb.toString();
  }

  public String findTextValue(Node node) {
    NodeList children = node.getChildNodes();
    if (children.getLength() == 1) {
      Node subNode = children.item(0);
      if (subNode.getNodeType() == Node.TEXT_NODE) {
        return subNode.getNodeValue();
      }
    }
    return null;
  }

  /**
   * whether the post is a draft post
   * 
   * @param we
   * @return true when we is a draft post
   */
  private boolean isDraft(WordpressEntry we) {
    return we.getDateCreated().getTime() == 943891200000l;
  }

  private Date parseDate(String str) throws ParseException {
    DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    Date d = df.parse(str);
    return d;
  }

  private Node createNamespaceContext(String[] namespaceMappings) {
    if ((namespaceMappings == null) || (namespaceMappings.length == 0)) {
      return null;
    }
    Document doc = new DocumentImpl();
    Element namespaceElement = doc.createElement("NamespaceMappings");
    String prefix = namespaceMappings[0];
    String uri = namespaceMappings[1];
    namespaceElement.setAttribute(prefix, uri);
    doc.appendChild(namespaceElement);
    return doc.getDocumentElement();
  }

  public File getRssFile() {
    return rssFile;
  }

  public void setRssFile(File rssFile) {
    this.rssFile = rssFile;
  }

  public static void main(String[] args) {
    RSSReader wc = new RSSReader();
    try {
      List<WordpressEntry> wes = wc.getRecentPosts(0, 3, true);
      for (int i = 0; i < wes.size(); i++) {
        WordpressEntry e = wes.get(i);
        System.out.println(e.getPostid());
        System.out.println(e.getTitle());
        Object[] os = e.getCategories();
        for (int j = 0; j < os.length; j++) {
          System.out.println(os[j]);
        }
        System.out.println(e.getDateCreated());
        System.out.println(e.getDescription());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
